package lxs.swift.collector;


import lxs.swift.collector.wrap.Option;
import lxs.swift.collector.wrap.Trouble;
import lxs.swift.fn.Func;
import lxs.swift.fn.Run;
import lxs.swift.fn.Supply;
import lxs.swift.operation.Try;
import org.jetbrains.annotations.NotNull;

import java.io.Serializable;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;
import java.util.function.BiFunction;
import java.util.function.Consumer;

/**
 * 封装了报错信息的数据容器
 * <pre><code>
 *  Res<String> respond = Res.exec(() -> {
 *      if (i > 0) throw new RuntimeException("aaa");
 *      if (i < 0) throw new RuntimeException("bbb");
 *      return "";
 *  }).error((err) -> {
 *      throw new RuntimeException("bbb");
 *  }).onFinally(System.out::println).respond();
 *  System.out.println(respond);
 * </code></pre>
 */
public class Result<T> implements Serializable {
    private static final long serialVersionUID = 1L;
    private final boolean isOk;
    private final T data;
    private final long timestamp;
    private final long consuming;
    private String message;
    private int code;
    private final Map<String, Object> extensions;


    private Result(boolean isOk, T data, String message, int code, long timestamp, long consuming, Map<String, Object> extensions) {
        this.isOk = isOk;
        this.data = data;
        this.message = message;
        this.code = code;
        this.timestamp = timestamp;
        this.consuming = consuming;
        this.extensions = extensions;
    }

    public boolean isOk() {
        return isOk;
    }

    public T getData() {
        return data;
    }

    public String getMessage() {
        return message;
    }

    public int getCode() {
        return code;
    }

    public long getTimestamp() {
        return timestamp;
    }

    public long getConsuming() {
        return consuming;
    }

    public Map<String, Object> getExtensions() {
        return extensions;
    }

    public Result<T> msg(String msg) {
        this.message = msg;
        return this;
    }

    public Result<T> code(int code) {
        this.code = code;
        return this;
    }

    public Result<T> pushExt(String key, Object ext) {
        this.extensions.put(key, ext);
        return this;
    }

    public <R, E extends Throwable> Result<R> map(Func<T, R, E> function) throws E {
        if (this.isOk()) {
            R v = function.apply(this.data);
            return new Result<>(true, v, message, code, timestamp, consuming, extensions);
        }
        return new Result<>(false, null, message, code, timestamp, consuming, extensions);
    }

    public <R, V> Result<R> andMap(@NotNull Result<V> wrapper, @NotNull BiFunction<T, V, R> combiner) {
        Objects.requireNonNull(wrapper);
        Objects.requireNonNull(combiner);
        String msg = this.message + ", " + wrapper.message;
        if (this.isOk() && wrapper.isOk()) {
            R v = combiner.apply(this.data, wrapper.data);
            return Result.ok(v).msg(msg);
        }
        return Result.err(msg);
    }

    public <R> Result<R> cloneInner(Result<R> other) {
        return new Result<>(other.isOk, other.getData(), other.getMessage(), other.getCode(), other.getTimestamp(), other.consuming, other.extensions);
    }

    public static <V, E extends Throwable> Executor<V, E> exec(Supply<V, E> supplier) {
        return new Executor<>(supplier);
    }

    public static <E extends Throwable> Executor<Void, E> exec(Run<E> runnable) {
        Supply<Void, E> supplier = () -> {
            runnable.run();
            return null;
        };
        return new Executor<>(supplier);
    }

    public static <V> Result<V> ok(V data) {
        return new Result<>(true, data, "", 200, System.currentTimeMillis(), 0, new LinkedHashMap<>());
    }

    public static <V> Result<V> success(String msg) {
        return new Result<>(true, null, msg, 200, System.currentTimeMillis(), 0, new LinkedHashMap<>());
    }

    public static <V> Result<V> err(String msg) {
        return new Result<>(false, null, msg, 500, System.currentTimeMillis(), 0, new LinkedHashMap<>());
    }


    public static <V> Result<V> of(Info<V> info, boolean isOk) {
        return new Result<>(isOk, info.data, info.message, info.code, System.currentTimeMillis(), 0, new LinkedHashMap<>());
    }

    public static <V, E extends Throwable> Result<V> of(Trouble<V, E> trouble) {
        try {
            V v = trouble.unwrap();
            return Result.ok(v);
        } catch (Throwable e) {
            return Result.err(e.getMessage());
        }
    }

    public static <V> Result<V> of(Option<V> option) {
        try {
            V v = option.unwrap();
            return Result.ok(v);
        } catch (Throwable e) {
            return Result.ok(null);
        }
    }

    public static <V, E extends Throwable> Result<V> of(@NotNull Supply<V, E> supply) {
        Objects.requireNonNull(supply);
        try {
            V v = supply.get();
            return Result.ok(v);
        } catch (Throwable e) {
            return Result.err(e.getMessage());
        }
    }

    public static class Info<V> {
        private final V data;
        private final String message;
        private final int code;

        public Info(V data, String message, int code) {
            this.data = data;
            this.message = message;
            this.code = code;
        }

        public V getData() {
            return data;
        }

        public String getMessage() {
            return message;
        }

        public int getCode() {
            return code;
        }
    }


    public static class Executor<V, E extends Throwable> {
        private final Supply<V, E> task;
        private Func<V, Info<V>, E> onSuccess;
        private Func<E, Info<Object>, E> onError;
        private Consumer<Result<V>> onFinally;//不暴露异常，必须自己处理
        private final long startMs = System.currentTimeMillis();


        private Executor(@NotNull Supply<V, E> task) {
            this.task = task;
            this.onSuccess = (data) -> new Info<>(data, "", 200);
            this.onError = (err) -> {
                err.printStackTrace();
                return new Info<>(null, err.getMessage(), 500);
            };
            this.onFinally = (res) -> {
            };
        }

        public Executor<V, E> success(@NotNull Func<V, Info<V>, E> onSuccess) {
            Objects.requireNonNull(onSuccess);
            this.onSuccess = onSuccess;
            return this;
        }

        public Executor<V, E> success(String msg, Integer code) {
            this.onSuccess = (data) -> new Info<>(data, msg, code);
            return this;
        }

        public Executor<V, E> success(String msg) {
            return success(msg, 200);
        }

        public Executor<V, E> success(Integer code) {
            return success(null, code);
        }

        public Executor<V, E> error(@NotNull Func<E, Info<Object>, E> onError) {
            Objects.requireNonNull(onError);
            this.onError = onError;
            return this;
        }

        public Executor<V, E> error(String msg, Integer code) {
            this.onError = (_e) -> new Info<>(null, msg, code);
            return this;
        }

        public Executor<V, E> error(String msg) {
            return error(msg, 500);
        }

        public Executor<V, E> error(Integer code) {
            return error(null, code);
        }

        public Executor<V, E> onFinally(@NotNull Consumer<Result<V>> onFinally) {
            Objects.requireNonNull(onFinally);
            this.onFinally = onFinally;
            return this;
        }

        public Result<V> respond() {
            return Try.valueOf(() -> {
                          V data = this.task.get();
                          Info<V> info = this.onSuccess.apply(data);
                          return Result.of(info, true);
                      }).caught((err) -> {
                          try {
                              Info<Object> info = this.onError.apply(err);
                              return (Result<V>) Result.of(info,false);
                          } catch (Throwable e) {
                              return Result.of(new Info<>(null, e.getMessage(), 500), false);
                          }
                      }).then(res -> {
                          this.onFinally.accept(res);
                          long endMs = System.currentTimeMillis();
                          return new Result<>(res.isOk, res.data, res.message, res.code, res.timestamp, endMs - startMs, res.extensions);
                      })
                      .ret();

        }
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Result<?> res = (Result<?>) o;
        return code == res.code && Objects.equals(data, res.data) && Objects.equals(message, res.message) && Objects.equals(extensions, res.extensions);
    }

    @Override
    public int hashCode() {
        return Objects.hash(data, message, code, extensions);
    }

    @Override
    public String toString() {
        if (this.isOk()) return "Result.Ok(data=" + data + ", message=" + this.message + ", code=" + this.code + ")";
        return "Result.Err(message=" + this.message + ", code=" + this.code + ")";
    }
}
